package hit.core;

import java.util.Iterator;
import java.util.Observable;

/**
 * A facade for all operations on the tree structure of the HIT system
 * */
public class TreeFacade extends Observable {
	private HomeInventoryTracker hit;
	
	/**
	 * Default Constructor
	 */
	public TreeFacade(){
		hit = HomeInventoryTracker.Instance();
	}
	/**
	 * @param hit the HomeInventoryTracker the TreeFacade is a facade for
	 * */
	public TreeFacade(HomeInventoryTracker hit) {
		this.hit = hit;
	}
	
	/**
	 * Edit a ProductGroup in the HIT tree
	 * @param name the new name for the ProductGroup
	 * @param threeMonth the new three month value for the ProductGroup
	 * @param before the original ProductGroup
	 * @return The edited {@link ProductGroup}
	 * @throws IllegalArgumentException if the new ProductGroup is invalid or before is invalid
	 * */
	public ProductGroup EditProductGroup(String name, Size threeMonth, ProductGroup before) 
														throws IllegalArgumentException {
		try{
			ProductGroup newGroup = new ProductGroup(name);
			newGroup.SetParentContainer(before.GetParentContainer());
			newGroup.SetThreeMonth(threeMonth);
			before.GetParentContainer().UpdateProductGroup(before, newGroup);
			TreeNotification notify = new TreeNotification();
			notify.SetModifiedContainer(newGroup);
			notify.SetOriginalContainer(before);
			
			this.setChanged();
			super.notifyObservers(notify);			
			return newGroup;
		}
		catch (IllegalArgumentException e){
			throw e;
		}
	}
	
	/**
	 * Add a ProductGroup to the HIT tree
	 * @param name the name for the new ProductGroup
	 * @param threeMonth the three month value for the ProductGroup
	 * @param parent the parent ProductContainer for the new ProductGroup
	 * @return The new {@link ProductGroup}
	 * @throws IllegalArgumentException thrown if the new ProductGroup is invalid or the parent
	 * is invalid.
	 * */
	public ProductGroup AddProductGroup(String name, Size threeMonth, ProductContainer parent) 
														throws IllegalArgumentException {
		try{
			ProductGroup newGroup = new ProductGroup(name);
			newGroup.SetParentContainer(parent);
			newGroup.SetThreeMonth(threeMonth);
			parent.AddProductGroup(newGroup);
			
			
			TreeNotification notify = new TreeNotification();
			notify.SetModifiedContainer(newGroup);
			notify.SetNotificationType(TreeNotification.Type.NODE_ADDED);
			
			this.setChanged();
			super.notifyObservers(notify);
			return newGroup;
		}
		catch (IllegalArgumentException e){
			throw e;
		}
	}
	
	/**
	 * Delete a ProductGroup from the HIT tree
	 * @param productGroup the ProductGroup to delete from the tree
	 * @return The removed {@link ProductGroup}
	 * @throws IllegalArgumentException thrown if productGroup cannot be removed
	 * */
	public ProductGroup DeleteProductGroup(ProductGroup productGroup) 
													throws IllegalArgumentException {
		if (productGroup.GetParentContainer().CanRemoveProductGroup(productGroup)){
			try{
				productGroup.GetParentContainer().RemoveProductGroup(productGroup);
				
				TreeNotification notify = new TreeNotification();
				notify.SetModifiedContainer(productGroup);
				notify.SetNotificationType(TreeNotification.Type.NODE_REMOVED);
			
				this.setChanged();
				super.notifyObservers(notify);		
				return productGroup;
			}
			catch (IllegalArgumentException e){
				throw e;
			}
		}
		else{
			throw new IllegalArgumentException();
		}
	}
	
	/**
	 * Edit a StorageUnit in the HIT tree
	 * @param name the new name for the StorageUnit
	 * @param before the original StorageUnit
	 * @return The edited {@link StorageUnit}
	 * @throws IllegalArgumentException thrown if the new name is invalid, or
	 * before was invalid.
	 * */
	public StorageUnit EditStorageUnit(String name, StorageUnit before) 
														throws IllegalArgumentException {
		try{		
			hit.GetStorageUnit(before.GetName()).SetName(name);
			TreeNotification notify = new TreeNotification();
			notify.SetModifiedContainer(before);
			notify.SetOriginalContainer(before);
			
			this.setChanged();
			super.notifyObservers(notify);
			
			return before;
		}
		catch (IllegalArgumentException e){
			throw e;
		}
	}
	
	/**
	 * Add a StorageUnit to the HIT tree
	 * @param name the name for the new StorageUnit
	 * @return The new {@link StorageUnit}
	 * @throws IllegalArgumentException thrown if name is invalid
	 * */
	public StorageUnit AddStorageUnit(String name) throws IllegalArgumentException {
		try{
			StorageUnit newStorage = new StorageUnit(name);
			hit.AddStorageUnit(newStorage);
			
			TreeNotification notify = new TreeNotification();
			notify.SetModifiedContainer(newStorage);
			notify.SetNotificationType(TreeNotification.Type.NODE_ADDED);
			
			this.setChanged();
			super.notifyObservers(notify);		
			return newStorage;
		}
		catch(IllegalArgumentException e){
			throw e;
		}
	}
	
	/**
	 * Determines if a  {@link StorageUnit} can be added to the tree
	 * @param storageUnit the StorageUnit to check
	 * @return whether the StorageUnit can be added
	 * */
	public boolean CanAddStorageUnit(StorageUnit storageUnit) {
		return hit.CanAddStorageUnit(storageUnit);
	}
	
	/**
	 * Delete a StorageUnit from the HIT tree
	 * @param storageUnit the StorageUnit to remove from the HIT tree
	 * @return The removed {@link StorageUnit}
	 * @throws IllegalArgumentException thrown if storageUnit cannot be deleted
	 * */
	public StorageUnit DeleteStorageUnit(StorageUnit storageUnit) throws IllegalArgumentException {
		if (hit.CanRemoveStorageUnit(storageUnit)){
			try{
				hit.RemoveStorageUnit(storageUnit);
				
				TreeNotification notify = new TreeNotification();
				notify.SetModifiedContainer(storageUnit);
				notify.SetNotificationType(TreeNotification.Type.NODE_REMOVED);
			
				this.setChanged();
				super.notifyObservers(notify);		
				return storageUnit;
			}
			catch (IllegalArgumentException e){
				throw e;
			}
		}
		else{
			throw new IllegalArgumentException();
		}
	}
	
	/**
	 * Determines if the new Name for a storage unit is valid.
	 * @param storageUnit The storage to be edited.
	 * @param name The new name for the new StorageUnit.
	 * @return True if the edit is valid, false otherwise.
	 */
	public boolean CanEditStorageUnit(StorageUnit storageUnit, String name) {
		
		if (storageUnit == null || !storageUnit.IsValid() || 
				name == null || name == "")
			return false;
		
		// Check if the StorageUnit is in the system.		
		StorageUnit temp =  hit.GetStorageUnit(storageUnit.name);
		
		if (temp == null)
			return false;
		
		// Checks if the name is already the same, or if no StorageUnit with the same name exists.
		return (storageUnit.GetName().equals(name) || hit.GetStorageUnit(name) == null);
	}
	
	/**
	 * Determines whether a given {@link ProductGroup} can be added.
	 * @param newProductGroup
	 * @return true if the product group can be added, false otherwise.
	 */
	public boolean CanAddProductGroup(ProductGroup newProductGroup){
		return newProductGroup.GetParentContainer().CanAddProductGroup(newProductGroup);
	}
	
	public Iterator<StorageUnit> GetStorageUnitIterator(){
		return hit.GetStorageUnitIterator();
	}
	
	public boolean CanRemoveStorageUnit(StorageUnit storageUnit) {
		return hit.CanRemoveStorageUnit(storageUnit);
	}
	
	public boolean CanRemoveProductGroup(ProductGroup productGroup) {
		return hit.CanRemoveProductContainer(productGroup);
	}
}
